(c) 2016 Justin Bois and Griffin Chure. This work is licensed under a Creative Commons Attribution License CC-BY 4.0. All code contained herein is licensed under an MIT license.
This tutorial was generated from a Jupyter notebook. You can download the notebook here.
import numpy as np
# Our image processing tools
import skimage.filters
import skimage.io
import skimage.measure
import skimage.morphology
import skimage.segmentation
import matplotlib.pyplot as plt
import seaborn as sns
rc={'lines.linewidth': 2, 'axes.labelsize': 18, 'axes.titlesize': 18}
sns.set(rc=rc)
# The following is specific Jupyter notebooks
%matplotlib inline
%config InlineBackend.figure_formats = {'png', 'retina'}
Now that we have learned how to do basic segmentation, we continue our image processing lessons to learn how to obtain quantitative data from images.
In this lesson, we will expand on what we learned in the first image processing tutorial and develop some further skills to help us with segmentation of images. The images we will use were acquired by Griffin Chure in Rob Phillips's lab here at Caltech. The bacteria are the HG105 E. coli strain, developed for an earlier paper from the Phillips lab. The strain is wild type, except the LacZYA genes are deleted. It features a YFP gene hooked up to the Lac promoter. Thus, the fluorescent signal is a measure of gene expression governed by the Lac promoter in the absence of several of its repressors.
In the previous lesson, we made strong arguments for performing segmentation on images of bacteria constitutively expressing a fluorescent protein. The main motivation is that the haloing effect from phase contrast images make segmenting bacteria in large clumps very difficult. However, for the images we are analyzing here, we do not have a constitutive fluorescent protein. It is a bad idea to use the fluorescent signal we are measuring to do the segmentation, since this could introduce bias, especially when we have very low fluorescent signal for some cells. So, in this experiment, we will do the segmentation using the phase image and then use the fluorescent image to get a measure of the fluorescence intensity for each bacterium. It is important to have a dilute field of bacteria so that we do not have clumps of bacteria that make segmentation difficult.
Importantly, we are free to manipulate the brightfield image as we like in order to get good segmentation. After we have identified which pixels below to which cells, we have to be very careful adjusting the fluorescent images as the pixel values in these images are the signal we are measuring. We will only employ a median filter with a very small structuring element to deal with the known camera issue of occasional rogue high intensity pixels.
Let's start by just getting a look at the images to see what we're dealing with here. A collection of images can be found in data/HG105_images which will be used for this lesson and the following practice section. The specific images we will be looking at here will be noLac_phase_0000.tif and noLac_FITC_0000.tif.
# Load the images
im_phase = skimage.io.imread('data/HG105_images/noLac_phase_0004.tif')
im_fl = skimage.io.imread('data/HG105_images/noLac_FITC_0004.tif')
# Display side-by-side
with sns.axes_style('dark'):
fig, ax = plt.subplots(1, 2, figsize=(9.5, 8))
ax[0].imshow(im_phase, cmap=plt.cm.viridis)
ax[1].imshow(im_fl, cmap=plt.cm.viridis)
Argh. We can see a few issues in these images. First, it appears that the illumination in the phase contrast image is not uniform and is darker at the top than on the bottom. Additionally, we can see again a couple rogue pixels are ruining viewing the fluorescent image. They also present a problem with the phase image, so we'll do a quick median filter on it to clean things up before we try to conquer the issue of uneven illumination.
# Structuring element
selem = skimage.morphology.square(3)
# Perform median filter
im_phase_filt = skimage.filters.median(im_phase, selem)
im_fl_filt = skimage.filters.median(im_fl, selem)
# Show the images again
with sns.axes_style('dark'):
fig, ax = plt.subplots(1, 2, figsize=(9.5, 8))
ax[0].imshow(im_phase_filt, cmap=plt.cm.viridis)
ax[1].imshow(im_fl_filt, cmap=plt.cm.viridis)
So, we will proceed to segment in phase and then use the fluorescence data to quantify expression levels. Before we begin our thresholding, however, we should correct our illumination issues in the phase contrast image which are likely caused by improper Köhler illumination.
We can correct for this non-uniform illumination by performing a Gaussian background subtraction. It is important for us to notice that the uneven illumination spans across a distance much greater than that of a single bacterium. In a Gaussian background subtraction, small variations in pixel value across the image are blurred using a two-dimensional Gaussian function leaving only large-scale variations in intensity. To correct for the non-uniformity of the illumination, we can simply subtract our Gaussian blurred image from our original.
# Apply a gaussian blur with a 50 pixel radius.
im_phase_gauss = skimage.filters.gaussian(im_phase_filt, 50.0)
# Show the two images side-by-side
with sns.axes_style('dark'):
fig, ax = plt.subplots(1,2, figsize=(9.5,8))
ax[0].imshow(im_phase_filt, cmap=plt.cm.viridis)
ax[1].imshow(im_phase_gauss, cmap=plt.cm.viridis)
While our input image has a uint16 data type, our Gaussian filtered image is actually a float64. This means in order to have any substantive effect, our original image must also be converted to a float64 before subtraction. We can use the skimage.img_as_float() function to perform this conversion.
# Convert the median-filtered phase image to a float64
im_phase_float = skimage.img_as_float(im_phase_filt)
# Subtract our gaussian blurred image from the original.
im_phase_sub = im_phase_float - im_phase_gauss
# Show our original image and background subtracted image side-by-side.
with sns.axes_style('dark'):
fig, ax = plt.subplots(1, 2, figsize=(9.5, 8))
ax[0].imshow(im_phase_float, cmap=plt.cm.viridis)
ax[1].imshow(im_phase_sub, cmap=plt.cm.viridis)